第十四章 信号量、共享内存和消息队列
本章讨论了一组进程间通信的机制,它们常被称为 IPC ( Inter-Process Communication ) 。
包括:
-
信号量:用于管理对资源的访问。
-
共享内存:用于在程序间高效地共享数据。
-
消息队列:在程序之间传递数据的一种简单方法。
Note
使用 ipcs 命令可以报告系统中的相关IPC信息。
信号量
程序可能拥有着所谓的临界代码,需要被确保只有一个进程(或一个执行线程)可以进入这个临界代码并拥有对资源独占式的访问。
信号量是一种特殊的变量,它只取正整数值,并且程序对其访问都是原子操作。只允许对它进行等待( wait )和发送信号( signal )两种操作,可以用 P 和 V 来代替(因 wait 和 signal 已经有特殊含义)。
Note
P 和 V 这俩字母来自荷兰单词,因为信号量的概念是荷兰计算机科学家 Dijkstra 提出的。
信号量的定义
最简单的信号量是只能取值0和1的变量,即二进制信号量。可以取多个正整数值的信号量被称为通用信号量。
假设有一个信号量变量 sv , PV 操作的含义是:
操作 | 含义 |
---|---|
P | 如果 sv 的值大于零,就给它减去1;如果它的值等于零,就挂起该进程的执行 |
V | 如果有其他进程因等待 sv 而被挂起,就让它恢复运行;如果没有进程因等待 sv 而被挂起,就给它加1 |
一个理论性的例子
两个进程共享信号量变量 sv 。一旦其中一个进程执行了 P(SV) 操作,它将得到信号量,并可以进入临界区域。而第二个进程将被阻止进入临界区域,因为当它试图执行 P(sv) 操作时,它会被挂起以等待第一个进程离开临界区域并执行 V(sv) 操作释放信号量。
用伪代码说明,则如:
semaphore sv = 1; loop forever { P(sv); critical code section; V(sv); noncritical code section; }
Linux 的信号量机制
所有的 Linux 信号量函数都是针对成组的通用信号进行操作,而不是只针对一个二进制信号量。本章仅讨论了单个信号量的使用,在绝大多数情况下,使用它就足够了。
semget 函数 可以获取信号量或创建信号量。
int semget(key_t key, int num_sems, int sem_flags);
key 用于获取系统维护的信号量,它返回的是信号量标识符 sem_id 。之后的信号量函数采用 sem_id 来作为句柄。
不相关的进程可以访问到同一个信号量。
semop 函数 用于改变信号量的值,即采取 PV 操作。
semctl 函数 用于直接控制信号量信息。它可以初始化信号量,或者删除信号量。
信号量如果不删除,它将继续在系统中存在。
共享内存
共享内存允许两个不相关的进程访问同一个逻辑内存。共享内存是在两个正在运行的进程之间传递数据的一种非常有效的方式。
共享内存是由 IPC 为进程创建的一个特殊的地址范围,它将出现在进程的地址空间中。其他进程可以将同一段共享内存连接到它们自己的地址空间中。
共享内存并未提供同步机制。在第一个进程结束对共享内存的写操作之前,并无自动的机制可以组织第二个进程开始对它进行读取。对共享内存访问的同步控制必须由程序员负责。
shmget 函数用来创建共享内存,或者获取一个已经创建好的共享内存。返回其标识符。
创建好共享内存后,它还不能被任何进程访问,必须将其连接到一个进程的地址空间中,这项工作由 shmat 函数完成。
shmdt 函数将共享内存从当前进程分离,但不会删除共享内存,而只是不能再被该进程访问。
shmctl 函数用于控制共享内存,它可以删除共享内存段。
消息队列
消息队列与命名管道有相似之处。
消息队列提供了一种从一个进程到另一个进程发送一个数据块的方法。每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据块。
好处是:我们可以通过发送消息来几乎完全避免命名管道的同步和阻塞问题。我们可以用一些方法来提前查看紧急消息。
不过,每个数据块都有一个最大长度限制,系统中所有队列所包含的全部数据块的总长度也有一个上限。
msgget 函数可以创建或访问一个消息队列。
msgsnd 和 msgrcv 用来发送、接收消息队列。
msgctl 用于控制消息队列,比如删除消息队列。